package com.hh.us.mongo.morphia.dao;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.PostConstruct;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.bson.types.ObjectId;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.jmkgreen.morphia.Datastore;
import com.github.jmkgreen.morphia.Key;
import com.github.jmkgreen.morphia.query.CriteriaContainer;
import com.github.jmkgreen.morphia.query.FieldEnd;
import com.github.jmkgreen.morphia.query.Query;
import com.github.jmkgreen.morphia.query.UpdateOperations;
import com.hh.us.mongo.morphia.entity.MorphiaEntity;
import com.hh.us.mongo.morphia.entity.Paging;
import com.hh.us.mongo.morphia.entity.PagingResult;
import com.hh.us.mongo.morphia.util.MongoPaginationUtil;

public class UsMongoBaseDaoImpl<E extends MorphiaEntity<I>, I> extends
		MorphiaBasicDao<E, I> implements IUsMongoBaseDao<E, I> {

	private static final Logger LOGGER = LoggerFactory.getLogger(UsMongoBaseDaoImpl.class);

	private Datastore datastore;

	public Datastore getDatastore() {
		return datastore;
	}

	public void setDatastore(Datastore datastore) {
		this.datastore = datastore;
	}

	@PostConstruct
	public void replaceDatastore() {
		super.setDatastore(datastore);
	}

	/** ----------查询--------- */

	@Override
	public long countByProperty(Map<String, Object> properties) {
		Query<E> query = super.createQuery();
		query = buildQuery(properties, null, null);
		return query.countAll();
	}

	@Override
	public List<E> findByProperty(Map<String, Object> conditions, int page,
			int pageSize) {
		if (page < 1) {
			page = 1;
		}

		if (pageSize < 1) {
			pageSize = 10;
		}
		int offset = (page - 1) * pageSize;
		Paging paging = new Paging(offset, pageSize);
		return buildQuery(conditions, paging, null).asList();
	}

	@Override
	public List<E> findByProperty(Map<String, Object> properties) {
		return buildQuery(properties, null, null).asList();
	}

	@Override
	public List<E> findByProperty(Paging paging) {
		return buildQuery(null, paging, null).asList();
	}

	@Override
	public List<E> findByProperty(Map<String, Object> properties, String orderBy) {
		return buildQuery(properties, null, orderBy).asList();
	}

	@Override
	public List<E> findByProperty(Map<String, Object> properties,
			Paging paging, String orderBy) {
		return buildQuery(properties, paging, orderBy).asList();
	}

	@Override
	public List<E> findByProperty(Map<String, Object> properties, Paging paging) {
		return buildQuery(properties, paging, null).asList();
	}

	@Override
	public E findOneByProperty(Map<String, Object> properties) {
		return buildQuery(properties, null, null).get();
	}

	@Override
	public E findOneByProperty(String property, Object value) {
		return buildQuery(property, value, null, null).get();
	}

	@Override
	public List<E> findByProperty(String property, Object value) {
		Query<E> query = buildQuery(property, value, null, null);
		return query.asList();
	}

	@Override
	public List<E> containByProperty(String property, Object value) {
		Query<E> query = buildContainQuery(property, value, null, null);
		return query.asList();
	}

	@Override
	public List<E> findByProperty(String property, Object value,
			String... retriveFields) {
		Query<E> query = buildQuery(property, value, null, null, retriveFields);
		return query.asList();
	}

	@Override
	public List<E> findByProperty(String property, Object value, String orderBy) {
		return buildQuery(property, value, null, orderBy).asList();
	}

	@Override
	public List<E> findByProperty(String property, Object value, Paging paging) {
		return buildQuery(property, value, paging, null).asList();
	}

	@Override
	public List<E> findByProperty(String property, Object value, Paging paging,
			String orderBy) {
		return buildQuery(property, value, paging, orderBy).asList();
	}

	protected Query<E> buildQuery(String property, Object value, Paging paging,
			String orderBy, String... retriveFields) {
		Query<E> query = super.createQuery();
		if (value != null) {
			query.filter(property, value);
		}
		if (paging != null) {
			query.offset(paging.getOffset()).limit(paging.getLimit());
		}
		if (StringUtils.isNotBlank(orderBy)) {
			query.order(orderBy);
		}
		if (retriveFields.length > 0) {
			query.retrievedFields(true, retriveFields);
		}
		return query;
	}

	/**
	 * 扩展此方法 可以在MAP中传field 的相应方法 同时可以传and和or
	 * MAP中的key传入"* and"或者"* or",value传相应的MAP,就满足了与或的操作
	 * 待验证,可能除filter方法以外的其他方式会有一些BUG
	 *
	 * @param properties
	 * @param paging
	 * @param orderBy
	 * @return
	 */
	protected Query<E> buildQuery(Map<String, Object> properties,
			Paging paging, String orderBy) {
		Query<E> query = super.createQuery();
		String[] defaultOperator = new String[] { "=", "==", "!=", "<>", ">",
				"<", ">=", "<=", "in", "nin", "all", "size", "exists" };
		if (properties != null && !properties.isEmpty()) {
			Iterator<Entry<String, Object>> it = properties.entrySet()
					.iterator();
			while (it.hasNext()) {
				Entry<String, Object> entry = it.next();
				if (StringUtils.isNotBlank(entry.getKey())) {
					String[] keyArray = StringUtils.split(entry.getKey(), " ");
					if (keyArray.length > 1) {
						if (ArrayUtils.contains(defaultOperator, keyArray[1])) {
							query.filter(entry.getKey(), entry.getValue());
						} else {
							if ("*".equals(keyArray[0])) {
								@SuppressWarnings("unchecked")
								Map<String, Object> subProperties = (Map<String, Object>) entry
										.getValue();
								switch (keyArray[1]) {
								case "and":
									query.and(buildOpCriteriaByMap(
											subProperties, query));
									break;
								case "or":
									query.or(buildOpCriteriaByMap(
											subProperties, query));
									break;
								default:
									break;
								}
							} else {
								try {
									Class<?> paramClass = Iterable.class;
									if (String.class.equals(entry.getValue()
											.getClass())) {
										paramClass = String.class;
									}
									if (Object.class.equals(entry.getValue()
											.getClass())) {
										paramClass = Object.class;
									}
									final Method method = FieldEnd.class
											.getMethod(keyArray[1], paramClass);
									method.invoke(query.field(keyArray[0]),
											entry.getValue());
								} catch (Exception e) {
									try {
										final Method method = FieldEnd.class
												.getMethod(keyArray[1],
														Object.class);
										method.invoke(
												query.criteria(keyArray[0]),
												entry.getValue());
									} catch (Exception e1) {
										LOGGER.warn(e1.getMessage(), e1);
									}
								}
							}
						}
					} else {
						query.filter(entry.getKey(), entry.getValue());
					}
				}
			}
		}
		if (paging != null) {
			query.offset(paging.getOffset()).limit(paging.getLimit());
		}
		if (StringUtils.isNotBlank(orderBy)) {
			query.order(orderBy);
		}
		return query;
	}

	private CriteriaContainer[] buildOpCriteriaByMap(
			Map<String, Object> properties, Query<E> query) {
		ArrayList<CriteriaContainer> criteriaList = buildCriteriaByMap(
				properties, query);
		CriteriaContainer[] arrayList = new CriteriaContainer[criteriaList
				.size()];
		criteriaList.toArray(arrayList);
		return arrayList;
	}

	private ArrayList<CriteriaContainer> buildCriteriaByMap(
			Map<String, Object> properties, Query<E> query) {
		ArrayList<CriteriaContainer> criteriaList = new ArrayList<CriteriaContainer>();
		Iterator<Entry<String, Object>> subIt = properties.entrySet()
				.iterator();
		while (subIt.hasNext()) {
			Entry<String, Object> subEntry = subIt.next();
			if (StringUtils.isNotBlank(subEntry.getKey())) {
				String[] subKeyArray = StringUtils
						.split(subEntry.getKey(), " ");
				if (subKeyArray.length > 1) {
					if ("*".equals(subKeyArray[0])) {
						@SuppressWarnings("unchecked")
						Map<String, Object> subProperties = (Map<String, Object>) subEntry
								.getValue();
						switch (subKeyArray[1]) {
						case "and":
							criteriaList.add(query.and(buildOpCriteriaByMap(
									subProperties, query)));
							break;
						case "or":
							criteriaList.add(query.or(buildOpCriteriaByMap(
									subProperties, query)));
							break;
						default:
							break;
						}
					} else {
						try {
							Class<?> paramClass = Iterable.class;
							if (String.class.equals(subEntry.getValue()
									.getClass())) {
								paramClass = String.class;
							}
							if (Object.class.equals(subEntry.getValue()
									.getClass())) {
								paramClass = Object.class;
							}
							final Method method = FieldEnd.class.getMethod(
									subKeyArray[1], paramClass);
							criteriaList.add((CriteriaContainer) method.invoke(
									query.criteria(subKeyArray[0]),
									subEntry.getValue()));
						} catch (Exception e) {
							try {
								final Method method = FieldEnd.class.getMethod(
										subKeyArray[1], Object.class);
								criteriaList.add((CriteriaContainer) method
										.invoke(query.criteria(subKeyArray[0]),
												subEntry.getValue()));
							} catch (Exception e1) {
								LOGGER.warn(e1.getMessage(), e1);
							}
						}
					}
				}
			}
		}
		return criteriaList;
	}

	protected Query<E> buildContainQuery(String property, Object value,
			Paging paging, String orderBy, String... retriveFields) {
		Query<E> query = super.createQuery();
		if (value != null) {
			query.field(property).contains(value.toString());
		}
		if (paging != null) {
			query.offset(paging.getOffset()).limit(paging.getLimit());
		}
		if (StringUtils.isNotBlank(orderBy)) {
			query.order(orderBy);
		}
		if (retriveFields.length > 0) {
			query.retrievedFields(true, retriveFields);
		}
		return query;
	}

	@Override
	public long getTotalCount() {
		return this.getDatastore().getCount(getEntityClass());
	}

	@Override
	public List<E> findAll() {
		return this.getDatastore().createQuery(getEntityClass()).asList();
	}

	@Override
	public E getById(String id) {
		return this.getDatastore().get(super.getEntityClass(),
				buildObjectId(id));
	}

	private PagingResult<E> buildPagingResult(Paging paging, List<E> list,
			long totalCount) {
		PagingResult<E> result = new PagingResult<E>();
		result.setPaging(paging);
		result.setResult(list);
		result.setTotalCount(totalCount);
		return result;
	}

	@Override
	public PagingResult<E> getPagingResult(String property, Object value,
			Paging paging) {
		Query<E> query = buildQuery(property, value, paging, null);
		return buildPagingResult(paging, query.asList(), query.countAll());
	}

	@Override
	public PagingResult<E> getPagingResult(String property, Object value,
			Paging paging, String orderBy) {
		Query<E> query = buildQuery(property, value, paging, orderBy);
		return buildPagingResult(paging, query.asList(), query.countAll());
	}

	@Override
	public PagingResult<E> getPagingResult(Map<String, Object> properties,
			Paging paging) {
		Query<E> query = buildQuery(properties, paging, null);
		return buildPagingResult(paging, query.asList(), query.countAll());
	}

	@Override
	public PagingResult<E> getPagingResult(Map<String, Object> properties,
			Paging paging, String orderBy) {
		Query<E> query = buildQuery(properties, paging, orderBy);
		return buildPagingResult(paging, query.asList(), query.countAll());
	}

	@Override
	public boolean exist(Map<String, Object> Properties) {
		E e = findOneByProperty(Properties);
		return e == null ? false : true;
	}

	@Override
	public boolean exist(String property, Object value) {
		E e = findOneByProperty(property, value);
		return e == null ? false : true;
	}

	@Override
	public boolean exist(String id) {
		E e = findOneByProperty("id", buildObjectId(id));
		return e == null ? false : true;
	}

	/** ----------保存--------- */
	/**
	 *
	 * 由于和MophiaBasicDao已经有create,save方法，此处返回值不同，所以不能重载，所以替换了方法名
	 *
	 * @param list
	 * @return
	 */

	@Override
	public boolean saveEntity(List<E> list) {
		try {
			this.getDatastore().save(list);
		} catch (RuntimeException ex) {
			LOGGER.error("数据库[保存]失败,当前时间:{},保存数据信息:{},错误详细信息:{}",
					new DateTime(), list.toString(), ex.getMessage());
			throw ex;
		}
		return true;
	}

	@Override
	public Iterable<Key<E>> saveEntityReturnKeys(List<E> list) {
		try {
			return this.getDatastore().save(list);
		} catch (RuntimeException ex) {
			LOGGER.error("数据库[保存]失败,当前时间:{},保存数据信息:{},错误详细信息:{}",
					new DateTime(), list.toString(), ex.getMessage());
			throw ex;
		}
	}

	/**
	 * 只能插入新的数据，不能更新
	 */
	@Override
	public String createEntity(E e) {
		try {
			return super.create(e).toString();
		} catch (RuntimeException ex) {
			LOGGER.error("数据库[保存]失败,当前时间:{},保存数据信息:{},错误详细信息:{}",
					new DateTime(), e.toString(), ex.getMessage());
			throw ex;
		}
	}

	/**
	 * 保存或更新
	 */
	@Override
	public String saveEntity(E e) {
		try {
			return super.createOrReplace(e).toString();
		} catch (RuntimeException ex) {
			LOGGER.error("数据库[保存]失败,当前时间:{},保存数据信息:{},错误详细信息:{}",
					new DateTime(), e.toString(), ex.getMessage());
			throw ex;
		}

	}

	/** ----------更新--------- */
	@Override
	public boolean updateEntity(E e, UpdateOperations<E> ops) {
		try {
			this.getDatastore().update(e, ops);
		} catch (RuntimeException ex) {
			LOGGER.error("数据库[更新]失败,当前时间:{},更新数据信息:{},错误详细信息:{}",
					new DateTime(), e.toString(), ex.getMessage());
			throw ex;
		}
		return true;
	}

	@Override
	public boolean updateEntity(Query<E> e, UpdateOperations<E> ops) {
		try {
			this.getDatastore().update(e, ops);
		} catch (RuntimeException ex) {
			LOGGER.error("数据库[更新]失败,当前时间:{},更新数据信息:{},错误详细信息:{}",
					new DateTime(), e.toString(), ex.getMessage());
			throw ex;
		}
		return true;
	}

	public boolean mergeEntity(E e) {
		try {
			this.getDatastore().merge(e);
		} catch (RuntimeException ex) {
			LOGGER.error("数据库[更新]失败,当前时间:{},更新数据信息:{},错误详细信息:{}",
					new DateTime(), e.toString(), ex.getMessage());
			throw ex;
		}
		return true;
	}

	/** ----------删除--------- */
	@Override
	public boolean deleteAll() {
		try {
			this.getDatastore().delete(createQuery());
		} catch (RuntimeException ex) {
			LOGGER.error("数据库[删除]失败,当前时间:{},错误详细信息:{}", new DateTime(),
					ex.getMessage());
			throw ex;
		}
		return true;
	}

	@Override
	public boolean deleteById(String id) {
		try {
			this.getDatastore().delete(super.getEntityClass(),
					buildObjectId(id));
		} catch (RuntimeException ex) {
			LOGGER.error("数据库[删除]失败,当前时间:{},删除数据Id:{},错误详细信息:{}",
					new DateTime(), id, ex.getMessage());
			throw ex;
		}
		return true;
	}

	@Override
	public boolean delete(Query<E> query) {
		try {
			this.getDatastore().delete(query);
		} catch (RuntimeException ex) {
			LOGGER.error("数据库[删除]失败,当前时间:{},删除条件信息:{},错误详细信息:{}",
					new DateTime(), query.toString(), ex.getMessage());
			throw ex;
		}
		return true;
	}

	/**
	 * idStr为monogodb的id字符串，24位 如果id不合法会抛出运行时异常
	 *
	 * @param idStr
	 * @return
	 */
	protected ObjectId buildObjectId(String idStr) {
		return new ObjectId(idStr);
	}

	@Override
	public List<E> findBetweenTime(int pPage, int pPageSize, Date pBeginDate,
			Date pEndDate) {
		Query<E> lvQuery = getDatastore().createQuery(getEntityClass()).order(
				"-updateDate");
		lvQuery = MongoPaginationUtil.generatePaginationQuery(lvQuery, pPage,
				pPageSize);
		lvQuery.filter("updateDate >= ", pBeginDate);
		lvQuery.filter("updateDate <= ", pEndDate);
		List<E> lvResult = lvQuery.asList();
		return lvResult;
	}

	@Override
	public List<E> findAfterTime(int pPage, int pPageSize, Date beginDate) {
		return findBetweenTime(pPage, pPageSize, beginDate, new Date());
	}

	@Override
	public List<E> findAllBetweenTime(Date pBeginDate, Date pEndDate) {
		Query<E> lvQuery = getDatastore().createQuery(getEntityClass()).order(
				"-updateDate");
		lvQuery.filter("updateDate >= ", pBeginDate);
		lvQuery.filter("updateDate <= ", pEndDate);
		List<E> lvResult = lvQuery.asList();
		return lvResult;
	}

	@Override
	public List<E> findAllAfterTime(Date pBeginDate) {
		return findAllBetweenTime(pBeginDate, new Date());
	}
}
